/*
* Copyright (c) 2009-2011 Lockheed Martin Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eurekastreams.web.client.ui.common.stream.renderers;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.eurekastreams.commons.formatting.DateFormatter;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.stream.ActivityDTO;
import org.eurekastreams.server.domain.stream.ActivityVerb;
import org.eurekastreams.server.domain.stream.BaseObjectType;
import org.eurekastreams.web.client.events.ChangeShowStreamRecipientEvent;
import org.eurekastreams.web.client.events.EventBus;
import org.eurekastreams.web.client.events.Observer;
import org.eurekastreams.web.client.events.ShowNotificationEvent;
import org.eurekastreams.web.client.events.data.DeletedActivityResponseEvent;
import org.eurekastreams.web.client.events.data.UpdatedActivityFlagResponseEvent;
import org.eurekastreams.web.client.jsni.EffectsFacade;
import org.eurekastreams.web.client.jsni.WidgetJSNIFacadeImpl;
import org.eurekastreams.web.client.model.ActivityModel;
import org.eurekastreams.web.client.model.FlaggedActivityModel;
import org.eurekastreams.web.client.model.requests.UpdateActivityFlagRequest;
import org.eurekastreams.web.client.ui.Session;
import org.eurekastreams.web.client.ui.common.dialog.Dialog;
import org.eurekastreams.web.client.ui.common.notifier.Notification;
import org.eurekastreams.web.client.ui.common.pagedlist.ItemRenderer;
import org.eurekastreams.web.client.ui.common.stream.comments.CommentsListPanel;
import org.eurekastreams.web.client.ui.common.stream.renderers.object.BookmarkRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.object.FileRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.object.NoteRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.object.ObjectRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.object.VideoRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.verb.PostRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.verb.ShareRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.verb.VerbRenderer;
import org.eurekastreams.web.client.ui.common.stream.share.ShareMessageDialogContent;
import org.eurekastreams.web.client.ui.pages.master.StaticResourceBundle;
import org.eurekastreams.web.client.utility.BaseActivityLinkBuilder;
import org.eurekastreams.web.client.utility.InContextActivityLinkBuilder;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.InlineHyperlink;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
/**
* Renders a message in the stream.
*/
public class StreamMessageItemRenderer implements ItemRenderer<ActivityDTO>
{
/**
* State.
*
*/
public enum State
{
/**
* Default.
*/
DEFAULT,
/**
* Read only.
*/
READONLY
}
/**
* Effects facade.
*/
private final EffectsFacade effects = new EffectsFacade();
/**
* Show the recipient.
*/
private ShowRecipient showRecipient;
/**
* Show the recipient.
*/
private ShowRecipient showRecipientInStream;
/** Show controls for managing flagged content. */
private boolean showManageFlagged;
/** Render specifically for a single-activity view. */
private boolean singleView;
/** If date should be a permalink. */
private boolean createPermalink = true;
/**
* State.
*/
private final State state;
/**
* Verb dictionary.
*/
private final Map<ActivityVerb, VerbRenderer> verbDictionary = new HashMap<ActivityVerb, VerbRenderer>();
/**
* Object dictionary.
*/
private final Map<BaseObjectType, ObjectRenderer> objectDictionary = new HashMap<BaseObjectType, ObjectRenderer>();
/**
* Flag to show new comment box in initial view.
*/
private boolean showComment = false;
/** For building links to activities. */
private BaseActivityLinkBuilder activityLinkBuilder = new InContextActivityLinkBuilder();
/**
* Constructor.
*
* @param inShowRecipient
* show the recipient.
*/
public StreamMessageItemRenderer(final ShowRecipient inShowRecipient)
{
this(inShowRecipient, State.DEFAULT);
}
/**
* Constructor.
*
* @param inShowRecipient
* show the recipient.
* @param inState
* state.
*/
public StreamMessageItemRenderer(final ShowRecipient inShowRecipient, final State inState)
{
showRecipientInStream = inShowRecipient;
state = inState;
verbDictionary.put(ActivityVerb.POST, new PostRenderer());
verbDictionary.put(ActivityVerb.SHARE, new ShareRenderer());
objectDictionary.put(BaseObjectType.BOOKMARK, new BookmarkRenderer());
objectDictionary.put(BaseObjectType.NOTE, new NoteRenderer());
objectDictionary.put(BaseObjectType.VIDEO, new VideoRenderer());
objectDictionary.put(BaseObjectType.FILE, new FileRenderer());
Session.getInstance().getEventBus().addObserver(ChangeShowStreamRecipientEvent.class,
new Observer<ChangeShowStreamRecipientEvent>()
{
public void update(final ChangeShowStreamRecipientEvent event)
{
showRecipientInStream = event.getValue();
}
});
}
/**
* Sets showComment.
*
* @param inShowComment
* value to set.
*/
public void setShowComment(final boolean inShowComment)
{
showComment = inShowComment;
}
/**
* @param inShowManageFlagged
* If the controls for managing flagged content should be shown.
*/
public void setShowManageFlagged(final boolean inShowManageFlagged)
{
showManageFlagged = inShowManageFlagged;
}
/**
* @param inSingleView
* Render specifically for a single-activity view.
*/
public void setSingleView(final boolean inSingleView)
{
singleView = inSingleView;
}
/**
* @param inActivityLinkBuilder
* Builder to use for activity links.
*/
public void setActivityLinkBuilder(final BaseActivityLinkBuilder inActivityLinkBuilder)
{
activityLinkBuilder = inActivityLinkBuilder;
}
/**
* Render a message item.
*
* @param msg
* the message item.
*
* @return the rendered item as a FlowPanel.
*/
public Panel render(final ActivityDTO msg)
{
if (msg.getDestinationStream().getUniqueIdentifier().equals(msg.getActor().getUniqueIdentifier()))
{
showRecipient = ShowRecipient.FOREIGN_ONLY;
}
else
{
showRecipient = showRecipientInStream;
}
Panel mainPanel = new FlowPanel();
mainPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().streamMessageItem());
mainPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().listItem());
mainPanel.addStyleName(state.toString());
VerbRenderer verbRenderer = verbDictionary.get(msg.getVerb());
verbRenderer.setup(objectDictionary, msg, state, showRecipient);
boolean doManageFlagged = showManageFlagged && !state.equals(State.READONLY) && msg.isDeletable();
// left column items
Panel leftColumn = null;
if (doManageFlagged)
{
leftColumn = new FlowPanel();
leftColumn.addStyleName(StaticResourceBundle.INSTANCE.coreCss().leftColumn());
mainPanel.add(leftColumn);
}
// avatar
Widget avatar = verbRenderer.getAvatar();
if (avatar != null)
{
Panel parent = leftColumn == null ? mainPanel : leftColumn;
parent.add(avatar);
}
if (doManageFlagged)
{
leftColumn.add(buildManageFlaggedControls(msg, mainPanel));
}
FlowPanel msgContent = new FlowPanel();
msgContent.addStyleName(StaticResourceBundle.INSTANCE.coreCss().description());
mainPanel.add(msgContent);
CommentsListPanel commentsPanel = null;
if (!state.equals(State.READONLY))
{
commentsPanel = new CommentsListPanel(msg.getFirstComment(), msg.getLastComment(), msg.getCommentCount(),
msg.getEntityId(), msg.isCommentable(), msg.getDestinationStream().getType(), msg
.getDestinationStream().getUniqueIdentifier(), activityLinkBuilder);
}
// row for who posted
Panel sourceMetaData = new FlowPanel();
sourceMetaData.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageMetadataSource());
for (StatefulRenderer itemRenderer : verbRenderer.getSourceMetaDataItemRenderers())
{
Widget metaDataItem = itemRenderer.render();
if (metaDataItem != null)
{
sourceMetaData.add(metaDataItem);
}
}
msgContent.add(sourceMetaData);
// content
FlowPanel nonMetaData = new FlowPanel();
nonMetaData.addStyleName(state.toString());
Widget content = verbRenderer.getContent();
if (content != null)
{
nonMetaData.add(content);
msgContent.add(nonMetaData);
}
// additional metadata
FlowPanel metaData = new FlowPanel();
metaData.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageMetadataAdditional());
for (StatefulRenderer itemRenderer : verbRenderer.getMetaDataItemRenderers())
{
Widget metaDataItem = itemRenderer.render();
if (metaDataItem != null)
{
metaData.add(metaDataItem);
}
}
if (metaData.getWidgetCount() > 0)
{
msgContent.add(metaData);
}
// timestamp and actions
Panel timestampActions = new FlowPanel();
timestampActions.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageTimestampActionsArea());
String date = new DateFormatter(new Date()).timeAgo(msg.getPostedTime());
Widget dateLink;
if (createPermalink)
{
String permalinkUrl = activityLinkBuilder.buildActivityPermalink(msg.getId(), msg.getDestinationStream()
.getType(), msg.getDestinationStream().getUniqueIdentifier());
dateLink = new InlineHyperlink(date, permalinkUrl);
}
else
{
dateLink = new InlineLabel(date);
}
dateLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageTimestampLink());
timestampActions.add(dateLink);
if (msg.getAppName() != null)
{
String appSource = msg.getAppSource();
if (appSource != null)
{
FlowPanel viaPanel = new FlowPanel();
viaPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().viaMetadata());
viaPanel.add(new InlineLabel("via "));
viaPanel.add(new Anchor(msg.getAppName(), appSource));
timestampActions.add(viaPanel);
}
else
{
InlineLabel viaLine = new InlineLabel("via " + msg.getAppName());
viaLine.addStyleName(StaticResourceBundle.INSTANCE.coreCss().viaMetadata());
timestampActions.add(viaLine);
}
// TODO: If appSource is not supplied, the link should go to the respective galleries for apps and plugins.
// However, the app galery requires knowing the start page tab id, and the worthwhile plugin gallery is only
// available to coordinators.
}
if (verbRenderer.getAllowLike())
{
LikeCountWidget likeCount = new LikeCountWidget(msg.getEntityId(), msg.getLikeCount(), msg.getLikers(), msg
.isLiked());
timestampActions.add(likeCount);
}
timestampActions.add(buildActions(msg, mainPanel, commentsPanel, verbRenderer));
msgContent.add(timestampActions);
// comments
if (commentsPanel != null)
{
mainPanel.add(commentsPanel);
if (msg.getComments() != null && !msg.getComments().isEmpty())
{
commentsPanel.renderAllComments(msg.getComments());
}
if (showComment)
{
commentsPanel.activatePostComment();
}
}
return mainPanel;
}
/**
* Builds the action links panel.
*
* @param msg
* The message.
* @param mainPanel
* The overall panel for the message.
* @param commentsPanel
* The comments panel.
* @param verbRenderer
* Renderer for the message's verb.
* @return The actions panel.
*/
private Widget buildActions(final ActivityDTO msg, final Panel mainPanel, final CommentsListPanel commentsPanel,
final VerbRenderer verbRenderer)
{
final EventBus eventBus = Session.getInstance().getEventBus();
Panel actionsPanel = new FlowPanel();
actionsPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().messageActionsArea());
// Comment
// The verb is used for activities that are always now commentable. The msg.isCOmmentable is used for activities
// that that normally are commentable but the user has turned off.
if (commentsPanel != null && verbRenderer.getAllowComment() && msg.isCommentable())
{
Label commentLink = new InlineLabel("Comment");
commentLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().linkedLabel());
actionsPanel.add(commentLink);
commentLink.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
commentsPanel.activatePostComment();
}
});
}
// Like
if (verbRenderer.getAllowLike())
{
insertActionSeparator(actionsPanel);
Widget like = new LikeWidget(msg.isLiked(), msg.getEntityId());
actionsPanel.add(like);
}
// Share
if (verbRenderer.getAllowShare() && msg.isShareable())
{
insertActionSeparator(actionsPanel);
Label shareLink = new InlineLabel("Share");
shareLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().linkedLabel());
actionsPanel.add(shareLink);
shareLink.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
onShare(msg);
}
});
}
// Flag (as inappropriate)
if (!state.equals(State.READONLY) && !showManageFlagged)
{
insertActionSeparator(actionsPanel);
Label link = new InlineLabel("Flag");
link.addStyleName(StaticResourceBundle.INSTANCE.coreCss().linkedLabel());
actionsPanel.add(link);
link.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
if (new WidgetJSNIFacadeImpl()
.confirm("Flagged activities will be sent to the organization coordinator for review. "
+ "Are you sure you want to flag this activity as inappropriate?"))
{
eventBus.addObserver(UpdatedActivityFlagResponseEvent.class,
new Observer<UpdatedActivityFlagResponseEvent>()
{
public void update(final UpdatedActivityFlagResponseEvent ev)
{
if (ev.getResponse() == msg.getId())
{
eventBus.removeObserver(ev, this);
eventBus.notifyObservers(new ShowNotificationEvent(new Notification(
"Activity has been flagged")));
}
}
});
FlaggedActivityModel.getInstance().update(new UpdateActivityFlagRequest(msg.getId(), true));
}
}
});
}
// Delete
if (!state.equals(State.READONLY) && msg.isDeletable())
{
insertActionSeparator(actionsPanel);
Label deleteLink = new InlineLabel("Delete");
deleteLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().linkedLabel());
actionsPanel.add(deleteLink);
setupDeleteClickHandler(deleteLink, msg, mainPanel);
}
// Save/Unsave
if (verbRenderer.getAllowStar() && msg.isStarred() != null)
{
insertActionSeparator(actionsPanel);
Widget star = new StarLinkWidget(msg.isStarred(), msg.getEntityId());
actionsPanel.add(star);
}
return actionsPanel;
}
/**
* Called when user requests to share the activity.
*
* @param msg
* Activity to share.
*/
protected void onShare(final ActivityDTO msg)
{
Dialog.showCentered(new ShareMessageDialogContent(msg));
}
/**
* Adds a separator (dot).
*
* @param panel
* Panel to put the separator in.
*/
private void insertActionSeparator(final Panel panel)
{
Label sep = new InlineLabel("\u2219");
sep.addStyleName(StaticResourceBundle.INSTANCE.coreCss().actionLinkSeparator());
panel.add(sep);
}
/**
* Sets up the buttons to manage flagged content.
*
* @param msg
* The activity.
* @param mainPanel
* The main activity panel.
* @return Panel with the controls.
*/
private Widget buildManageFlaggedControls(final ActivityDTO msg, final Panel mainPanel)
{
final Panel buttonsPanel = new FlowPanel();
buttonsPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().flagControls());
Label ignoreButton = new Label("Ignore");
ignoreButton.addStyleName(StaticResourceBundle.INSTANCE.coreCss().flagIgnoreButton());
buttonsPanel.add(ignoreButton);
ignoreButton.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent ev)
{
buttonsPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().waitActive());
if (singleView)
{
Session.getInstance().getEventBus().addObserver(UpdatedActivityFlagResponseEvent.class,
new Observer<UpdatedActivityFlagResponseEvent>()
{
public void update(final UpdatedActivityFlagResponseEvent ev)
{
if (ev.getResponse().equals(msg.getId()))
{
Session.getInstance().getEventBus().removeObserver(ev, this);
buttonsPanel.removeFromParent();
}
}
});
}
FlaggedActivityModel.getInstance().update(new UpdateActivityFlagRequest(msg.getId(), false));
}
});
Label deleteButton = new Label("Delete");
deleteButton.addStyleName(StaticResourceBundle.INSTANCE.coreCss().flagDeleteButton());
buttonsPanel.add(deleteButton);
deleteButton.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
if (new WidgetJSNIFacadeImpl().confirm("Are you sure you want to delete this activity?"))
{
buttonsPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().waitActive());
setupDeleteFadeout(msg, mainPanel);
ActivityModel.getInstance().delete(msg.getId());
}
}
});
return buttonsPanel;
}
/**
* Wires up the handler for clicking on a delete link/button.
*
* @param widget
* The delete link/button.
* @param msg
* The activity.
* @param mainPanel
* The main activity panel.
*/
private void setupDeleteClickHandler(final HasClickHandlers widget, final ActivityDTO msg, final Panel mainPanel)
{
widget.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
if (new WidgetJSNIFacadeImpl().confirm("Are you sure you want to delete this activity?"))
{
setupDeleteFadeout(msg, mainPanel);
performDelete(msg);
}
}
});
}
/**
* Action to actually do the delete.
*
* @param msg
* The activity.
*/
protected void performDelete(final ActivityDTO msg)
{
if (msg.getDestinationStream().getType() == EntityType.RESOURCE)
{
ActivityModel.getInstance().hide(msg.getId());
}
else
{
ActivityModel.getInstance().delete(msg.getId());
}
}
/**
* Sets up to remove the activity on deletion.
*
* @param msg
* The activity.
* @param mainPanel
* The main activity panel.
*/
private void setupDeleteFadeout(final ActivityDTO msg, final Panel mainPanel)
{
Session.getInstance().getEventBus().addObserver(DeletedActivityResponseEvent.class,
new Observer<DeletedActivityResponseEvent>()
{
public void update(final DeletedActivityResponseEvent ev)
{
if (ev.getResponse() == msg.getId())
{
effects.fadeOut(mainPanel.getElement(), true);
Session.getInstance().getEventBus().removeObserver(ev, this);
}
}
});
}
/**
* @param inCreatePermalink
* the createPermalink to set
*/
public void setCreatePermalink(final boolean inCreatePermalink)
{
createPermalink = inCreatePermalink;
}
/**
* @return the objectDictionary
*/
protected Map<BaseObjectType, ObjectRenderer> getObjectDictionary()
{
return objectDictionary;
}
/**
* @return the verbDictionary
*/
protected Map<ActivityVerb, VerbRenderer> getVerbDictionary()
{
return verbDictionary;
}
}